import datetime
import plistlib
import re
import shutil
import subprocess
import time
import os
from mobileprovision import MobileProvisionModel

import FileToolController


class AutoBuildIPA(object):
    projectName = ""
    isXcworkspace = None
    exportDirectory = ""
    projectFolder = ""

    def __init__(self, projectPath, mobileprovisionPath, bundleId="", appDisplayName="", appVersion="",
                 appBuildVersion="", isAppStore=False, projectName="", outputPath="", updateLogSignal=None):
        self.projectPath = projectPath
        self.mobileprovisionPath = mobileprovisionPath
        self.isAppStore = isAppStore
        self.bundleId = bundleId
        self.appDisplayName = appDisplayName
        self.appVersion = appVersion
        self.appBuildVersion = appBuildVersion
        self.outputPath = outputPath
        self.updateLogSignal = updateLogSignal
        self.projectName = projectName

    def start(self):
        if not os.path.exists(self.projectPath):
            self.dealPrintMsg("项目路径为空, 请检查后重试", 2)
            return

        projectSuffixName = os.path.splitext(self.projectPath)[1]
        # 判断是处理项目空间还是项目
        self.isXcworkspace = projectSuffixName == '.xcworkspace'
        oldProjectName = FileToolController.backFileNameAndFolderName(self.projectPath, 3)
        if self.projectName == "" or not self.isXcworkspace:
            # 项目名称
            self.projectName = oldProjectName

        self.projectFolder = os.path.dirname(self.projectPath)
        newProjectFolderPath = os.path.join(os.path.dirname(self.projectFolder),
                                            os.path.basename(self.projectFolder) + "_temp" + str(int(time.time())))
        shutil.copytree(os.path.dirname(self.projectPath), newProjectFolderPath)
        self.projectFolder = newProjectFolderPath
        self.projectPath = os.path.join(newProjectFolderPath, oldProjectName + projectSuffixName)

        self.exportDirectory = self.projectFolder + '/auto_archive'  # 输出的路径

        # self.createExportOptions()
        # self.clean()
        self.setConfigAppInfo()

        # shutil.rmtree(self.projectFolder)

    # 设置配置信息
    def setConfigAppInfo(self):
        newProjectPath = self.projectPath

        if self.isXcworkspace:
            for root, dirs, files in os.walk(self.projectFolder):
                for dirName in dirs:
                    if dirName == (self.projectName + ".xcodeproj"):
                        newProjectPath = os.path.join(root, dirName)
                        break
        pbxprojPath = os.path.join(newProjectPath, "project.pbxproj")
        if not os.path.exists(pbxprojPath):
            self.dealPrintMsg("project.pbxproj文件不存在,无法配置项目,跳过")
        else:
            self.dealPrintMsg("===========开始配置应用信息操作===========")
            newProjectFolder = os.path.dirname(newProjectPath)
            newProjectFolderPath = os.path.join(newProjectFolder, self.projectName)
            infoFilePath = os.path.join(newProjectFolderPath, "info.plist")

            if os.path.exists(infoFilePath):
                with open(infoFilePath, 'rb') as fp:
                    plist_data = plistlib.load(fp)

                if self.bundleId != "":
                    plist_data["CFBundleIdentifier"] = self.bundleId

                if self.appDisplayName != "":
                    plist_data["CFBundleDisplayName"] = self.appDisplayName

                if self.appVersion != "":
                    plist_data["CFBundleShortVersionString"] = self.appVersion

                if self.appBuildVersion != "":
                    plist_data["CFBundleVersion"] = self.appBuildVersion

                with open(infoFilePath, 'wb') as fp:
                    plistlib.dump(plist_data, fp)
            else:
                self.dealPrintMsg("无法自动定位info.plist文件,请手动修改配置参数.")

                # INFOPLIST_KEY_CFBundleDisplayName : AppName
                # PRODUCT_BUNDLE_IDENTIFIER : BundleId
                # CURRENT_PROJECT_VERSION : build
                # MARKETING_VERSION : version
                with open(pbxprojPath, 'r') as file:
                    fileTempData = file.read()

                    configArr = [self.bundleId, self.appDisplayName, self.appVersion, self.appBuildVersion]
                    configIndexArr = ["PRODUCT_BUNDLE_IDENTIFIER", "INFOPLIST_KEY_CFBundleDisplayName",
                                      "MARKETING_VERSION", "CURRENT_PROJECT_VERSION"]
                    for i in range(len(configArr)):
                        targetConfigStr = configArr[i]
                        targetConfigIndexStr = configIndexArr[i]
                        if targetConfigStr != "" and str(fileTempData).find(targetConfigIndexStr):
                            if targetConfigIndexStr == "INFOPLIST_KEY_CFBundleDisplayName":
                                targetConfigStr = "\"%s\"" % targetConfigStr
                            newBundleIdStr = targetConfigIndexStr + " = " + targetConfigStr + ";"
                            pattern = r'%s(.*?);' % targetConfigIndexStr
                            fileTempData = re.sub(pattern, newBundleIdStr, fileTempData, flags=re.DOTALL | re.MULTILINE)

                    fileData = fileTempData

                # 写入project.pbxproj文件
                with open(pbxprojPath, 'w', encoding='utf-8') as file:
                    file.write(fileData)

            with open(pbxprojPath, 'r') as file:
                fileTempData = file.read()
                mpModel = MobileProvisionModel(self.mobileprovisionPath)

                targetDic = {"DEVELOPMENT_TEAM": str(mpModel.team_identifier),
                             "PROVISIONING_PROFILE_SPECIFIER": str(mpModel.name)}
                for key, value in targetDic.items():
                    targetPattern = re.compile(r'(%s.*=.(.*?);)' % key)
                    targetResult = list(set(targetPattern.findall(fileTempData)))
                    for targetArr in targetResult:
                        if len(targetArr) >= 2:
                            oldTeamStr = str(targetArr[0])
                            replaceStr = str(targetArr[1])
                            if replaceStr == "\"\"" or replaceStr == value:
                                continue
                            newTeamStr = oldTeamStr.replace(replaceStr, "%s" % value)
                        fileTempData = fileTempData.replace(oldTeamStr, newTeamStr)

                fileData = fileTempData

            # 写入project.pbxproj文件
            with open(pbxprojPath, 'w', encoding='utf-8') as file:
                file.write(fileData)

            if self.bundleId == "":
                self.bundleId = re.search(r"PRODUCT_BUNDLE_IDENTIFIER.=.(.*?);",
                                          fileData).group(1).replace(" ", "")
            self.dealPrintMsg("===========配置应用信息操作完成===========")

        self.clean()

    def clean(self):
        self.dealPrintMsg("===========开始clean操作===========")
        start = time.time()
        cleanOpt = ('xcodebuild clean "%s" "%s" -scheme "%s" -configuration Release' %
                    (self.getXcworkspaceStr(), self.projectPath, self.projectName))
        result, isError = self.executeCmd(cleanOpt)
        end = time.time()

        # clean 结果
        if isError:
            self.dealPrintMsg("%s===========clean失败,用时:%.2f秒===========" % (result, end - start), 2)
        else:
            self.dealPrintMsg(result, 4)
            self.dealPrintMsg("===========clean成功,用时:%.2f秒===========" % (end - start))
            self.buildProj()
            # self.archive()

    def buildProj(self):
        self.dealPrintMsg("===========开始build操作===========")
        start = time.time()

        buildOpt = ('xcodebuild build "%s" "%s" -scheme "%s" -configuration Release -destination "generic/platform=iOS"'
                    % (self.getXcworkspaceStr(), self.projectPath, self.projectName))
        result, isError = self.executeCmd(buildOpt)
        end = time.time()

        # build 结果
        if isError:
            self.dealPrintMsg("%s===========build失败,用时:%.2f秒===========" % (result, end - start), 2)
        else:
            self.dealPrintMsg(result, 4)
            self.dealPrintMsg("===========build成功,用时:%.2f秒===========" % (end - start))
            buildPath = self.projectFolder + "/build"
            if os.path.exists(self.projectFolder + "/build"):
                shutil.rmtree(buildPath)
            self.archive()

    def archive(self):
        self.dealPrintMsg("===========开始archive操作===========")
        subprocess.call(['rm', '-rf', '%s' % self.exportDirectory])
        time.sleep(1)
        subprocess.call(['mkdir', '-p', '%s' % self.exportDirectory])
        time.sleep(1)

        start = time.time()

        # mpModel = MobileProvisionModel(self.mobileprovisionPath)
        # commonNameStr = str(mpModel.developer_certificates).split(",")[0].split("[Common Name: ")[-1]

        archiveOpt = ('xcodebuild archive "%s" "%s" -scheme "%s" -configuration Release -archivePath "%s/%s" '
                      '-destination "generic/platform=iOS"' %
                      (self.getXcworkspaceStr(), self.projectPath, self.projectName, self.exportDirectory,
                       self.projectName))

        result, isError = self.executeCmd(archiveOpt)
        end = time.time()

        # archive 结果
        if isError:
            subprocess.call(['rm', '-rf', '%s' % self.exportDirectory])
            self.dealPrintMsg("%s===========archive失败,用时:%.2f秒===========" % (result, end - start), 0)
        else:
            self.dealPrintMsg(result, 4)
            self.dealPrintMsg("===========archive成功,用时:%.2f秒===========" % (end - start))
            # self.export()
            self.createExportOptions()

        # 删除archive.xcarchive文件
        # subprocess.call(['rm', '-rf', '%s' % self.exportDirectory])

    def createExportOptions(self):
        self.dealPrintMsg("===========开始处理plist文件操作===========")

        mpModel = MobileProvisionModel(self.mobileprovisionPath)
        # app-store，ad-hoc，enterprise, development
        isDev = mpModel.entitlements.get('aps-environment') != "production"
        methodStr = "development" if isDev else ("app-store-connect" if self.isAppStore else "ad-hoc")
        # print(methodStr)
        signingCertificateStr = "Apple Development" if isDev else "Apple Distribution"

        fileStr = '''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>destination</key>
    <string>export</string>
    <key>manageAppVersionAndBuildNumber</key>
    <false/>
    <key>method</key>
    <string>%s</string>
    <key>provisioningProfiles</key>
    <dict>
        <key>%s</key>
        <string>%s</string>
    </dict>
    <key>signingCertificate</key>
    <string>%s</string>
    <key>signingStyle</key>
    <string>manual</string>
    <key>stripSwiftSymbols</key>
    <true/>
    <key>teamID</key>
    <string>%s</string>
    <key>uploadSymbols</key>
    <false/>
    <key>generateAppStoreInformation</key>
    <true/>
</dict>
</plist>
        ''' % (methodStr, self.bundleId, mpModel.name, signingCertificateStr, mpModel.app_id_prefix)
        with open(self.exportDirectory + "/ExportOptions.plist", 'w+') as file:
            file.write(fileStr)
        self.dealPrintMsg("===========处理plist文件操作完成===========")
        self.export()

    def export(self):
        self.dealPrintMsg("===========开始export操作===========")
        start = time.time()
        exportIpaFileName = self.projectName + " " + datetime.datetime.now().strftime("%Y-%m-%d %H-%M-%S")

        if self.outputPath == "":
            exportIpaPath = os.path.join(os.path.dirname(self.projectFolder), exportIpaFileName)
        else:
            if not os.path.exists(self.outputPath):
                os.makedirs(self.outputPath)
            exportIpaPath = os.path.join(self.outputPath, exportIpaFileName)

        exportOpt = (('xcodebuild -exportArchive -archivePath "%s/%s.xcarchive" -exportPath "%s" '
                      '-exportOptionsPlist "%s/ExportOptions.plist"') %
                     (self.exportDirectory, self.projectName, exportIpaPath, self.exportDirectory))
        result, isError = self.executeCmd(exportOpt)
        end = time.time()

        # ipa导出结果
        if isError:
            self.dealPrintMsg("%s===========导出IPA失败,用时:%.2f秒===========" % (result, end - start), 2)
        else:
            self.dealPrintMsg(result, 4)
            self.dealPrintMsg("===========导出IPA成功,用时:%.2f秒===========" % (end - start))

        self.dealPrintMsg("导出IPA文件路径为 === %s" % exportIpaPath)
        self.dealPrintMsg("Successfully completed all work.", 3)

    def getIsXcworkspace(self):
        projectSuffixName = os.path.splitext(self.projectPath)[1]

        return projectSuffixName == '.xcworkspace'

    def getXcworkspaceStr(self):
        return "-workspace" if self.isXcworkspace else "-project"

    @staticmethod
    def executeCmd(cmd):
        """
        指定终端指令
        :param cmd: 指令字符串
        :return: 返回输出字符串, 判断是否报错
        """
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
        output, stderrOutput = p.communicate()

        output = output.decode()
        resultCode = p.returncode
        return output, resultCode != 0

    def dealPrintMsg(self, msg, status=1):
        """
        处理打印行为
        :param status: 状态
        :param msg: 打印信息
        :return: 无返回值
        """

        if self.updateLogSignal is not None:
            self.updateLogSignal.emit(msg, status)
        else:
            print(msg)


if __name__ == '__main__':
    # projectPath1 = "/Users/yfzx/Documents/HaiWProj/NewProj.xcworkspace"  # 项目路径
    projectPath1 = "/Users/yfzx/Test/Test.xcodeproj"  # 项目路径
    mobileprovisionPath1 = "/Users/yfzx/Documents/mpfile/com_all2.mobileprovision"# 证书路径
    projectName1 = "Test" # 编译项目名称

    archive = AutoBuildIPA(projectPath1, mobileprovisionPath1, isAppStore=False, projectName=projectName1)
    archive.start()
